package cn.vonce.validator.intercept;

import cn.vonce.validator.annotation.VBean;
import cn.vonce.validator.config.ResultConfig;
import cn.vonce.validator.enumerate.ResultCode;
import cn.vonce.validator.enumerate.ResultType;
import cn.vonce.validator.exception.ValidatorException;
import cn.vonce.validator.helper.ValidatorHelper;
import cn.vonce.validator.model.BeanResult;
import cn.vonce.validator.model.FieldResult;
import cn.vonce.validator.utils.ReflectJdkUtil;
import cn.vonce.validator.utils.RequestDataUtil;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;

/**
 * 校验字段拦截器
 *
 * @author Jovi
 * @version 1.0
 * @email imjovi@qq.com
 * @date 2017年4月20日下午23:56:08
 */
public class ValidatorInterceptor implements MethodInterceptor {

    private final Logger logger = LoggerFactory.getLogger(ValidatorInterceptor.class);

    @Override
    public Object invoke(MethodInvocation arg0) throws Throwable {
        String fullName = arg0.getMethod().getReturnType().getSimpleName() + " " + arg0.getThis().getClass().getName() + "." + arg0.getMethod().getName();
        if (fullName.indexOf("$$") > -1) {
            return arg0.proceed();
        }
        if (arg0.getArguments().length == 0) {
            return arg0.proceed();
        }
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        BeanResult beanResult = new BeanResult(true, "校验通过");
        for (int i = 0; i < arg0.getArguments().length; i++) {
            // 获取实际对象-即为原始对象(可能是Bean，也可能是字段)
            Object object = arg0.getArguments()[i];
            Annotation[] annotations = arg0.getMethod().getParameterAnnotations()[i];
            if (request == null && object instanceof HttpServletRequest) {
                request = (HttpServletRequest) object;
            }
            if (response == null && object instanceof HttpServletResponse) {
                response = (HttpServletResponse) object;
            }
            // 优先校验bean
            VBean validBean = ValidatorHelper.getAnnotation(annotations, VBean.class);
            if (validBean != null) {
                beanResult = ValidatorHelper.validBean(object, validBean.group(), validBean.interrupt());
                break;
            }
            // 校验有注解的字段
            List<FieldResult> validFieldResultList = ValidatorHelper.valid(annotations, arg0.getMethod().getName() + "方法第" + (i + 1) + "个参数", object, null, "", true);
            if (!validFieldResultList.isEmpty()) {
                beanResult = new BeanResult("校验存在" + validFieldResultList.size() + "条错误", validFieldResultList);
                break;
            }
        }
        logger.info("正在校验参数: " + fullName);
        logger.info("请求URL参数: " + RequestDataUtil.getParameters(request.getParameterMap()));
        if (!beanResult.isPass()) {
            String tips = beanResult.getFieldResultList().get(0).getTips();
            logger.warn(beanResult.getMessage() + "，详情请看: " + beanResult.getFieldResultList());
            logger.info("参数校验不通过: " + fullName);
            WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
            ResultConfig resultConfig = null;
            if (webApplicationContext != null) {
                String[] names = webApplicationContext.getBeanNamesForType(ResultConfig.class);
                if (names != null && names.length > 0) {
                    resultConfig = (ResultConfig) webApplicationContext.getBean(names[0]);
                }
            }
            //未配置
            if (resultConfig == null) {
                this.defaultResult(tips, request, response);
            }
            //存在抛出异常配置
            if (resultConfig != null && resultConfig.getResultType() == ResultType.THROW_TIPS) {
                throw new ValidatorException("参数校验失败'" + tips + "'");
            }
            //存在返回结果配置
            if (resultConfig != null && resultConfig.getResultType() == ResultType.RETURN_TIPS) {
                //如果未配置模板
                if (resultConfig.getResultTemplate() == null) {
                    return this.resultAdapter(arg0.getMethod().getReturnType(), tips, resultConfig.getCodeFieldName(), resultConfig.getMsgFieldName());
                }
                //存放模板类的class以及实现的接口class
                List<Class<?>> classList = new ArrayList<>();
                classList.add(resultConfig.getResultTemplate().getClass());
                classList.addAll(Arrays.asList(resultConfig.getResultTemplate().getClass().getInterfaces()));
                //只要该方法返回的类型是模板类或者模板类的父接口即可返回结果
                if (classList.contains(arg0.getMethod().getReturnType())) {
                    //配置中的信息
                    Object resultTemplate = resultConfig.getResultTemplate();
                    Class<?> clazz = resultConfig.getResultTemplate().getClass();
                    String codeFieldName = resultConfig.getCodeFieldName();
                    String msgFieldName = resultConfig.getMsgFieldName();
                    Object result = copy(resultTemplate);
                    if (result instanceof Map) {
                        Object code = ReflectJdkUtil.instance().invoke(clazz, resultTemplate, "get", codeFieldName);
                        Object msg = ReflectJdkUtil.instance().invoke(clazz, resultTemplate, "get", msgFieldName);
                        if (code == null) {
                            ReflectJdkUtil.instance().invoke(clazz, result, "put", new Class[]{Object.class, Object.class}, new Object[]{codeFieldName, ResultCode.BAD.getCode()});
                        }
                        if (msg == null) {
                            ReflectJdkUtil.instance().invoke(clazz, result, "put", new Class[]{Object.class, Object.class}, new Object[]{msgFieldName, tips});
                        }
                    } else {
                        Object code = ReflectJdkUtil.instance().get(clazz, resultTemplate, codeFieldName);
                        Object msg = ReflectJdkUtil.instance().get(clazz, resultTemplate, msgFieldName);
                        if (code == null) {
                            ReflectJdkUtil.instance().set(clazz, result, codeFieldName, ResultCode.BAD.getCode());
                        }
                        if (msg == null) {
                            ReflectJdkUtil.instance().set(clazz, result, msgFieldName, tips);
                        }
                    }
                    return result;
                }
            }
        }
        logger.info("参数校验通过: " + fullName);
        return arg0.proceed();
    }

    /**
     * 适配合适的结果返回
     *
     * @param clazz
     * @param tips
     * @param codeFieldName
     * @param msgFieldName
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Object resultAdapter(Class<?> clazz, String tips, String codeFieldName, String msgFieldName) throws InstantiationException, IllegalAccessException {
        switch (clazz.getName()) {
            case "java.lang.String":
                return tips;
            case "java.util.Map":
            case "java.util.HashMap":
                return this.mapResult(tips, codeFieldName, msgFieldName);
            case "com.alibaba.fastjson.JSONObject":
            case "cn.hutool.json.JSONObject":
                return this.jsonOrMapResult(tips, clazz, codeFieldName, msgFieldName);
            default:
                Object result = clazz.newInstance();
                ReflectJdkUtil.instance().set(clazz, result, codeFieldName, ResultCode.BAD.getCode());
                ReflectJdkUtil.instance().set(clazz, result, msgFieldName, tips);
                return result;
        }
    }

    /**
     * Map结果返回
     *
     * @param tips
     * @param codeFieldName
     * @param msgFieldName
     * @return
     */
    private Map<String, Object> mapResult(String tips, String codeFieldName, String msgFieldName) {
        Map<String, Object> data = new HashMap<>();
        data.put(codeFieldName, ResultCode.BAD.getCode());
        data.put(msgFieldName, tips);
        return data;
    }

    /**
     * JSON或Map类型的结果返回
     *
     * @param tips
     * @param clazz
     * @param codeFieldName
     * @param msgFieldName
     * @return
     */
    private Object jsonOrMapResult(String tips, Class<?> clazz, String codeFieldName, String msgFieldName) {
        Object data = null;
        try {
            data = clazz.newInstance();
            ReflectJdkUtil.instance().invoke(clazz, data, "put", new Class[]{Object.class, Object.class}, new Object[]{codeFieldName, ResultCode.BAD.getCode()});
            ReflectJdkUtil.instance().invoke(clazz, data, "put", new Class[]{Object.class, Object.class}, new Object[]{msgFieldName, tips});
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 默认返回结果
     *
     * @param tips
     * @param request
     * @param response
     */
    private void defaultResult(String tips, HttpServletRequest request, HttpServletResponse response) {
        PrintWriter printWriter = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            printWriter = response.getWriter();
            String data = "{\"code\":" + ResultCode.BAD.getCode() + ",\"msg\":\"" + tips + "\"}";
            printWriter.write(data);
            this.logger.info(request.getServletPath() + " 的响应内容：" + data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            printWriter.close();
        }
    }

    /**
     * 复制对象
     *
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private Object copy(Object target) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(target);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        return ois.readObject();
    }

}
